﻿using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using Smartstore.Caching;
using Smartstore.Core;
using Smartstore.Core.Checkout.Tax;
using Smartstore.Core.Common;
using Smartstore.Core.Common.Services;
using Smartstore.Core.Customers;
using Smartstore.Core.Data;
using Smartstore.Core.Localization;
using Smartstore.Core.Stores;
using Smartstore.Core.Web;

namespace Smartstore.Web
{
    public partial class WebWorkContext : IWorkContext
    {
        private readonly SmartDbContext _db;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly ILanguageResolver _languageResolver;
        private readonly IStoreContext _storeContext;
        private readonly ICurrencyService _currencyService;
        private readonly IGenericAttributeService _attrService;
        private readonly TaxSettings _taxSettings;
        private readonly PrivacySettings _privacySettings;
        private readonly LocalizationSettings _localizationSettings;
        private readonly ICacheManager _cache;
        private readonly Lazy<ITaxService> _taxService;
        //private readonly IUserAgent _userAgent;
        private readonly IWebHelper _webHelper;
        private readonly IGeoCountryLookup _geoCountryLookup;

        private TaxDisplayType? _taxDisplayType;
        private Language _language;
        private Customer _customer;
        private Currency _currency;
        private Customer _impersonator;
        private bool? _isAdminArea;

        public WebWorkContext(
            SmartDbContext db,
            IHttpContextAccessor httpContextAccessor,
            ILanguageResolver languageResolver,
            IStoreContext storeContext,
            ICurrencyService currencyService,
            IGenericAttributeService attrService,
            TaxSettings taxSettings,
            PrivacySettings privacySettings,
            LocalizationSettings localizationSettings,
            Lazy<ITaxService> taxService,
            ICacheManager cache,
            IWebHelper webHelper,
            IGeoCountryLookup geoCountryLookup)
        {
            // TODO: (core) Implement WebWorkContext
            _db = db;
            _httpContextAccessor = httpContextAccessor;
            _languageResolver = languageResolver;
            _storeContext = storeContext;
            _currencyService = currencyService;
            _attrService = attrService;
            _taxSettings = taxSettings;
            _privacySettings = privacySettings;
            _taxService = taxService;
            _localizationSettings = localizationSettings;
            //_userAgent = userAgent;
            _cache = cache;
            _webHelper = webHelper;
            _geoCountryLookup = geoCountryLookup;
        }

        public Customer CurrentCustomer 
        {
            get 
            {
                if (_customer != null)
                {
                    return _customer;
                }

                _customer = _db.Customers.FirstOrDefault();

                return _customer;
            }
            set => _customer = value; 
        }

        public Customer CurrentImpersonator => _impersonator;

        public Language WorkingLanguage 
        {
            get
            {
                if (_language == null)
                {
                    var customer = CurrentCustomer;

                    // Resolve the current working language
                    _language = _languageResolver.ResolveLanguageAsync(customer, _httpContextAccessor.HttpContext).Await();

                    // Set language if current customer langid does not match resolved language id
                    var customerAttributes = customer.GenericAttributes;
                    if (customerAttributes.LanguageId != _language.Id)
                    {
                        SetCustomerLanguage(_language.Id);
                    }
                }

                return _language;
            }
            set
            {
                SetCustomerLanguage(value?.Id);
                _language = null;
            }
        }

        private void SetCustomerLanguage(int? languageId)
        {
            var customer = CurrentCustomer;

            if (customer == null || customer.IsSystemAccount)
                return;

            customer.GenericAttributes.LanguageId = languageId;
            customer.GenericAttributes.SaveChanges();
        }

        public Currency WorkingCurrency
        {
            get
            {
                if (_currency != null)
                {
                    return _currency;
                }

                var query = _db.Currencies.AsNoTracking();

                Currency currency = null;

                // Return primary store currency when we're in admin area/mode
                if (IsAdminArea)
                {
                    currency = _storeContext.CurrentStore.PrimaryStoreCurrency;
                }

                if (currency == null)
                {
                    // Find current customer language
                    var customer = CurrentCustomer;
                    var storeCurrenciesMap = query.ApplyStandardFilter(storeId: _storeContext.CurrentStore.Id).ToDictionary(x => x.Id);

                    if (customer != null && !customer.IsSearchEngineAccount())
                    {
                        // Search engines should always crawl by primary store currency
                        var customerCurrencyId = customer.GenericAttributes.CurrencyId;
                        if (customerCurrencyId > 0)
                        {
                            if (storeCurrenciesMap.TryGetValue(customerCurrencyId.Value, out currency))
                            {
                                currency = VerifyCurrency(currency);
                                if (currency == null)
                                {
                                    SetCustomerCurrency(null);
                                }
                            }
                        }
                    }

                    // if there's only one currency for current store it dominates the primary currency
                    if (storeCurrenciesMap.Count == 1)
                    {
                        currency = storeCurrenciesMap[storeCurrenciesMap.Keys.First()];
                    }

                    // Default currency of country to which the current IP address belongs.
                    if (currency == null)
                    {
                        var ipAddress = _webHelper.GetClientIpAddress();
                        var lookupCountry = _geoCountryLookup.LookupCountry(ipAddress);
                        if (lookupCountry != null)
                        {
                            var country = _db.Countries
                                .AsNoTracking()
                                .Include(x => x.DefaultCurrency)
                                .ApplyIsoCodeFilter(lookupCountry.IsoCode)
                                .FirstOrDefault();

                            if (country?.DefaultCurrency?.Published == true)
                            {
                                currency = country.DefaultCurrency;
                            }
                        }
                    }

                    // Find currency by domain ending
                    if (currency == null)
                    {
                        var request = _httpContextAccessor.HttpContext?.Request;
                        if (request != null)
                        {
                            currency = storeCurrenciesMap.Values.GetByDomainEnding(request.Host.Value);
                        }
                    }

                    // Get PrimaryStoreCurrency
                    if (currency == null)
                    {
                        currency = VerifyCurrency(_storeContext.CurrentStore.PrimaryStoreCurrency);
                    }

                    // Get the first published currency for current store
                    if (currency == null)
                    {
                        currency = storeCurrenciesMap.Values.FirstOrDefault();
                    }
                }

                // If not found in currencies filtered by the current store, then return any currency
                if (currency == null)
                {
                    currency = query.ApplyStandardFilter().FirstOrDefault();

                }

                // No published currency available (fix it)
                if (currency == null)
                {
                    currency = query.AsTracking().ApplyStandardFilter(true).FirstOrDefault();
                    if (currency != null)
                    {
                        currency.Published = true;
                        _db.SaveChanges();
                    }
                }

                _currency = currency;
                return _currency;
            }
            set 
            {
                SetCustomerCurrency(value?.Id);
                _currency = null;
            }
        }

        private static Currency VerifyCurrency(Currency currency)
        {
            if (currency != null && !currency.Published)
            {
                return null;
            }

            return currency;
        }

        private void SetCustomerCurrency(int? currencyId)
        {
            var customer = CurrentCustomer;
            customer.GenericAttributes.CurrencyId = currencyId;
            customer.GenericAttributes.SaveChanges();
        }

        public TaxDisplayType TaxDisplayType 
        {
            get => GetTaxDisplayTypeFor(CurrentCustomer, _storeContext.CurrentStore.Id);
            set => _taxDisplayType = value;
        }

        public TaxDisplayType GetTaxDisplayTypeFor(Customer customer, int storeId)
        {
            return TaxDisplayType.IncludingTax;
        }

        public bool IsAdminArea 
        { 
            get => false; 
            set => _isAdminArea = value; 
        }
    }
}
